/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.daydream.template.sequence.segment;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.pajamas.daydream.template.sequence.serializer.ToStringSerializer;
import org.pajamas.protocol.Serializer;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * @author william
 * @since 2024/4/7
 */
public class SegmentTokenSplitter implements SegmentDefinitionParser {
    private static final AbstractTokenSplitRule<Void> TOKEN_SPLIT_RULE_END = new EndSerializerSplitterRule();
    private final AbstractTokenSplitRule<String> keyTokenSplitRule;
    private final AbstractTokenSplitRule<String> defaultValueTokenSplitRule;
    private final AbstractTokenSplitRule<Serializer> valueSerializerTokenSplitRule;

    public SegmentTokenSplitter(@Nullable AbstractTokenSplitRule<String> keyTokenSplitRule,
                                @Nullable AbstractTokenSplitRule<String> defaultValueTokenSplitRule,
                                @Nullable AbstractTokenSplitRule<Serializer> valueSerializerTokenSplitRule) {
        this.keyTokenSplitRule = MoreObjects.firstNonNull(keyTokenSplitRule, new KeySplitterRule());
        this.defaultValueTokenSplitRule = MoreObjects.firstNonNull(defaultValueTokenSplitRule,
                new defaultValueSplitterRule());
        this.valueSerializerTokenSplitRule = MoreObjects.firstNonNull(valueSerializerTokenSplitRule,
                new valueSerializerSplitterRule());
    }

    public SegmentTokenSplitter() {
        this(null, null, null);
    }

    @Nonnull
    protected Map<String, String> collectOnce(@Nonnull String expression) {
        List<Pair<String, Integer>> pairs = Lists.newArrayList(this.keyTokenSplitRule.getSeparator(),
                        this.defaultValueTokenSplitRule.getSeparator(),
                        this.valueSerializerTokenSplitRule.getSeparator(),
                        TOKEN_SPLIT_RULE_END.getSeparator())
                .stream()
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .distinct()
                .filter(expression::contains)
                .map(separator -> Pair.of(separator, expression.indexOf(separator)))
                .sorted(Map.Entry.comparingByValue())
                .collect(Collectors.toList());
        if (StringUtils.isBlank(expression) || CollectionUtils.isEmpty(pairs)) {
            return Collections.emptyMap();
        }

        Map<String, String> result = Maps.newHashMap();
        for (int idx = 0; idx < pairs.size() - 1; ) {
            Pair<String, Integer> cur = pairs.get(idx);
            Pair<String, Integer> nxt = pairs.get(++idx);
            int bgnIdx = cur.getValue() + cur.getKey().length();
            if (bgnIdx < nxt.getValue()) {
                String subStr = expression.substring(bgnIdx, nxt.getValue());
                result.put(cur.getKey(), subStr);
            }
        }
        return result;
    }

    @Nonnull
    @Override
    public SegmentDefinition parse(@Nonnull String expression) {
        Map<String, String> mapping = this.collectOnce("{" + Objects.requireNonNull(expression) + "}");
        if (CollectionUtils.isEmpty(mapping)) {
            throw new IllegalArgumentException("expression is a illegal argument, SegmentDefinition fail to create");
        }

        String keyToken = mapping.get(this.keyTokenSplitRule.getSeparator());
        String defaultValueToken = mapping.get(this.defaultValueTokenSplitRule.getSeparator());
        String valueSerializerToken = mapping.get(this.valueSerializerTokenSplitRule.getSeparator());

        SegmentDefinition definition = SegmentDefinition
                .newBuilder()
                .key(this.keyTokenSplitRule.apply(keyToken))
                .defaultValue(this.defaultValueTokenSplitRule.apply(defaultValueToken))
                .valueSerializer(this.valueSerializerTokenSplitRule.apply(valueSerializerToken))
                .build();
        if (StringUtils.isBlank(definition.getKey())) {
            throw new IllegalArgumentException("key parsed is blank, illegal SegmentDefinition not allow to be " +
                    "exposed");
        }
        return definition;
    }

    private static class KeySplitterRule extends AbstractTokenSplitRule<String> {
        private KeySplitterRule() {
            super("{");
        }

        @Override
        public String apply(String string) {
            return string;
        }
    }

    private static class defaultValueSplitterRule extends AbstractTokenSplitRule<String> {
        private defaultValueSplitterRule() {
            super(":");
        }

        @Override
        public String apply(String string) {
            return MoreObjects.firstNonNull(string, "");
        }
    }

    private static class valueSerializerSplitterRule extends AbstractTokenSplitRule<Serializer> {

        private valueSerializerSplitterRule() {
            super(">>");
        }

        @Override
        public Serializer apply(String string) {
            return new ToStringSerializer();
        }
    }

    private static class EndSerializerSplitterRule extends AbstractTokenSplitRule<Void> {
        protected EndSerializerSplitterRule() {
            super("}");
        }

        @Override
        public Void apply(String s) {
            return null;
        }
    }
}
